package p.ithorns.framework.common.runtime;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringJoiner;
import java.util.function.Consumer;

/**
 * RuntimeExec
 * Java执行系统脚本
 *
 * 需要注意的是exec在Windows和Linux下的指令区别
 * 建议使用String[] 方式；
 * Windows使用new String[]{"cmd", "/c", xxxCmd}
 * Linux使用new String[]{"bash", "-c", xxxCmd}
 *
 * @author ithorns
 * @version 1.0.0
 * @date 2022/12/28 17:16
 */
public class RuntimeExec implements Runnable {

    private final static Logger log = LoggerFactory.getLogger(RuntimeExec.class);

    private final Object command;

    private final Consumer<String> consumer;


    public RuntimeExec(Object command) {
        this(command, null);
    }

    public RuntimeExec(Object command, Consumer<String> consumer) {
        this.command = command;
        this.consumer = consumer;
    }

    @Override
    public void run() {
        Process process = null;
        int exitVal = 0;
        try {
            if (null == command) {
                return;
            }

            log.info("执行指令 ==> {}", command.toString());
            if (command instanceof String[]) {
                process = Runtime.getRuntime().exec((String[]) command);
            } else if (command instanceof String) {
                process = Runtime.getRuntime().exec((String) command);
            } else {
                throw new UnsupportedOperationException("Runtime.getRuntime().exec()：不支持String[]和String之外的参数类型");
            }

            // Runtime.exec()创建的子进程公用父进程的流，不同平台上，父进程的stream buffer可能被打满导致子进程阻塞，从而永远无法返回。
            // 针对这种情况，我们只需要将子进程的stream重定向出来即可。
            new ExecStreamThread(process.getInputStream(), consumer).start();
            new ExecStreamThread(process.getErrorStream(), true, log::warn).start();

            exitVal = process.waitFor();
        } catch (IOException | InterruptedException e) {
            log.error(e.getMessage(), e);
        }

        if (exitVal != 0) {
            throw new RuntimeException("RuntimeExec任务执行失败");
        }

        log.info("执行成功 ==> {}", this.toString());
    }

    class ExecStreamThread extends Thread {
        InputStream is;
        boolean err = false;
        Consumer<String> consumer;

        ExecStreamThread(InputStream is, Consumer<String> consumer) {
            this(is, false, consumer);
        }

        ExecStreamThread(InputStream is, boolean err, Consumer<String> consumer) {
            this.is = is;
            this.err = err;
            this.consumer = consumer;
        }

        @Override
        public void run() {
            try (
                    InputStreamReader isr = new InputStreamReader(is);
                    BufferedReader br = new BufferedReader(isr);
            ) {
                String line;
                StringJoiner joiner = new StringJoiner("\r\n");
                while ((line = br.readLine()) != null) {
                    joiner.add(line);
                }

                if (err) {
                    log.warn("ERR ==> {}", joiner.toString());
                    return;
                }

                if (null != consumer) {
                    consumer.accept(joiner.toString());
                }
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    @Override
    public String toString() {
        if (command instanceof String[]) {
            return Arrays.toString((String[]) command);
        }

        return command.toString();
    }
}